/*
 * @Author: Songjq
 * @Date: 2021-12-16 21:19:35
 * @Desscription: 登录模块
 */
const Router = require("koa-router");
const svgCaptcha = require("svg-captcha");
const bcrypt = require("bcryptjs");
const tools = require("../../config/tools");
const jsencrypt = require("../../config/jsencrypt"); // 解密
const jwt = require("jsonwebtoken");
const keys = require("../../config/keys");
const router = new Router();
const sendEmail = require("../../tools/sendEmail.js"); //引入封装好的函数
const { randomCode, sendCode } = require("../../tools/smsFunction");

// 引入相关模板
const User = require("../../models/system/User");
const Role = require("../../models/system/Role");

// 引入input验证
const validateRegisterInput = require("../../validation/register");
const validateLoginInput = require("../../validation/login");

// 引入公共方法
const equipmentInfo = require("../../tools/equipmentInfo"); // 获取设备信息
const logininforFn = require("../../tools/logininfor"); // 写入登录日志方法

// 存储认证信息 - 全局变量
global.imgText = null;
global.confInfo = {
    emailCode: null, // 邮箱验证码
    expire: null, // 发起时间
};


/**
 * @route GET api/code
 * @desc  获取验证码
 * @access 接口是公开的
 */
router.get("/code", async (ctx, next) => {
    // 1.字母验数字证码
    // let codeConfig = {
    //     size: 4,// 验证码长度
    //     ignoreChars: '0o1i', // 验证码字符中排除 0o1i
    //     noise: 5, // 干扰线条的数量
    //     width: 100, //验证码宽度
    //     height: 40, //验证码高度
    //     fontSize: 40,   //字体大小
    //     color: true,    //开启字体颜色
    //     background: '#cc9966',//背景颜色
    //     // charPreset:'abcd123456789', //随机预设字符
    // }
    // const cap = svgCaptcha.create(codeConfig); //验证码

    // 2.数学表算式验证码
    let codeConfig = {
        mathMin: 1, //数学表达式的最小值
        mathMax: 20, // 数学表达式的最大值
        mathOperator: "+", // 使用的运算符:+、-或+-(用于随机的+或-)
        noise: 1, // 干扰线条的数量
        width: 160, //验证码宽度
        height: 60, //验证码高度
        fontSize: 60, //字体大小
        color: true, //开启字体颜色
        background: "#cc9966", //背景颜色
    };
    const cap = svgCaptcha.createMathExpr(codeConfig); //算式验证码

    let img = cap.data; // 验证码
    global.imgText = cap.text.toLowerCase(); // 验证码字符，忽略大小写（存取值，用于登录验证）
    ctx.response.type = "image/svg+xml"; // 设置响应头
    ctx.body = img;
});


/**
 * @route POST api/register
 * @desc  注册接口地址
 * @access 接口是公开的
 */
router.post("/register", async (ctx) => {
    const { errors, isValid } = validateRegisterInput(ctx.request.body);
    const { code, userno, password, username, role } = ctx.request.body;
    // 1.正则校验 - 判断是否验证通过 
    if (!isValid) {
        ctx.status = 400;
        ctx.body = errors;
        return;
    }

    // 2.校验验证码(字母转小写，然后对比校验)
    if (code.toLowerCase() !== global.imgText) {
        ctx.status = 400;
        ctx.body = { message: "验证码不正确!" };
        return;
    }

    // 3.存储到数据库
    const findResult = await User.findOne({ userno });
    if (findResult) {
        ctx.status = 400;
        ctx.body = { code: 400, message: "账号已被占用" };
    } else {
        const newUser = new User(
            Object.assign({ ...ctx.request.body }, {
                password: tools.enbcrypt(jsencrypt.decrypt(password)),
                role, // 开启用户自动选择角色注册
                // role: "ordinary", // 注册默认普通用户
                status: "0", // 默认启用
            })
        );

        // 存储到数据库
        await newUser
            .save()
            .then(() => {
                ctx.body = {
                    code: 200,
                    message: "新用户注册成功",
                    data: {
                        userno,
                        username,
                    },
                };
            })
            .catch((err) => {
                console.log(err);
            });
    }
});


/**
 * @route POST api/login
 * @desc  登录接口地址 返回token
 * @access 接口是公开的
 */
router.post("/login", async (ctx) => {
    const { errors, isValid } = validateLoginInput(ctx.request.body);
    const { loginType, userno, password, uuid, code, email } = ctx.request.body;
    const uuids = JSON.parse(jsencrypt.decrypt(uuid));

    // 1.获取用户ip 地理位置  浏览器信息
    global.loginInfo = {
        ipaddr: uuids.ipaddr,
        location: uuids.location,
        browser: uuids.browser,
    };

    // 2.判断是否验证通过
    if (!isValid) {
        ctx.status = 400;
        ctx.body = Object.assign({ code: 400 }, errors);
        // 写入登录日志
        logininforFn({
            userno,
            loginType,
            email,
            status: 1,
            msg: errors.message,
        });
        return;
    }

    // 3.校验验证码(字母转小写，然后对比校验)
    if (code.toLowerCase() !== global.imgText) {
        ctx.status = 400;
        ctx.body = { code: 400, message: "验证码不正确!" };
        logininforFn({ // 写入登录日志
            userno,
            loginType,
            email,
            status: 1,
            msg: "验证码不正确",
        });
        return;
    }

    // 4.判断用户是否存在
    const searchData = loginType == "account" ? { userno } : { email };
    const findUserResult = await User.findOne(searchData);

    if (!findUserResult) { // 用户不存在，终止执行
        ctx.status = 404;
        ctx.body = { code: 404, message: "用户不存在" };
        // 写入登录日志
        logininforFn({
            userno,
            loginType,
            email,
            status: 1,
            msg: "用户不存在",
        });
        return;
    }

    // 5.用户存在 - 验证用户状态，如果处于禁用状态，禁止访问
    if (findUserResult.status == 1) {
        ctx.status = 400;
        ctx.body = { code: 400, message: "当前账号已被禁用，请联系管理员" };
        return;
    }

    // 6.验证密码（对比密码是否一致）
    var result = await bcrypt.compareSync(
        jsencrypt.decrypt(password),
        findUserResult.password
    );

    // 6.1密码一致，验证通过，返回token
    if (result) {
        const roleResult = await Role.find({ roleKey: findUserResult.role });
        const token = jwt.sign(
            Object.assign({
                    id: findUserResult.id,
                    username: findUserResult.username,
                    userno: findUserResult.userno,
                },
                global.loginInfo
            ),
            keys.secretOrKey, { expiresIn: 3600 }
        );

        // 权限列表
        let roles = [...roleResult[0].permissions, ...[findUserResult.role]];

        // 登陆成功
        ctx.status = 200;
        ctx.body = {
            code: 200,
            token: "Bearer " + token,
            user: {
                id: findUserResult.id,
                userno: findUserResult.userno,
                username: findUserResult.username,
                role: findUserResult.role,
                avatar: findUserResult.avatar,
                roles: roles,
            },
            message: "登陆成功",
        };

        // 写入登录日志
        logininforFn({
            userno,
            loginType,
            email,
            status: 0,
            msg: "登陆成功",
        });
    } else {

        // 6.2密码校验失败
        ctx.status = 400;
        ctx.body = { code: 400, message: "密码错误" };
        // 写入登录日志
        logininforFn({
            userno,
            loginType,
            email,
            status: 1,
            msg: "密码错误",
        });
    }
});


/**
 * @route GET api/equipment/info
 * @desc  获取电脑信息
 * @access 接口是公开的
 */
router.get("/equipment/info", async (ctx, next) => {
    ctx.body = {
        status: 200,
        success: true,
        body: equipmentInfo,
    };
});


/**
 * @route POST api/email
 * @desc  发送邮箱验证码
 * @access 接口是公开的
 */
router.post("/email", async (ctx, next) => {
    const { code, email } = ctx.request.body;

    // 1.校验验证码(字母转小写，然后对比校验)
    if (code.toLowerCase() !== global.imgText) {
        ctx.status = 400;
        ctx.body = { code: 400, message: "验证码不正确!" };
        return;
    }

    // 2.校验发送频率，频率--1分钟内1次
    if (global.confInfo.expire && +new Date() - global.confInfo.expire < 0) {
        ctx.status = 400;
        ctx.body = { code: 400, message: "验证请求过于频繁，1分钟内1次" };
        return;
    }

    // 3.校验Email邮箱格式，出错时或者为空时
    if (!email) {
        ctx.status = 400;
        ctx.body = { code: 400, message: "参数错误" };
        return;
    }

    // 4.通过邮箱获取用户名称  => 仅作展示用
    const findResult = await User.findOne({ email });
    let username = findResult ? findResult.username : email;

    // 5.配置参数
    const conf = {
        get code() {
            // 验证码
            return () => {
                return Math.random().toString(16).slice(2, 6).toUpperCase(); //生成随机验证码
            };
        },
        get expire() {
            // 到期时间
            return () => {
                return +new Date() + 60 * 1000;
            };
        },
    };

    // 6.保存发送认证信息
    global.confInfo = {
        emailCode: conf.code(),
        expire: conf.expire(),
    };

    async function timeout() {
        return new Promise((resolve, reject) => {
            sendEmail.sendMail(
                username,
                email,
                global.confInfo.emailCode,
                (state) => {
                    resolve(state);
                }
            );
        });
    }

    // 7.发送验证码
    await timeout().then((state) => {
        if (state) {
            ctx.body = { code: 200, message: "验证码发送成功" };
        } else {
            ctx.status = 400;
            ctx.body = { code: 400, message: "验证码发送失败，请重新尝试" };
        }
    });
});


/**
 * @route POST api/forgetPwd
 * @desc  忘记密码？  通过绑定的邮箱找回密码
 * @access 接口是私有的
 */
router.post("/forgetPwd", async (ctx) => {
    const { email, password, emailCode, code } = ctx.request.body;

    let nowDate = +new Date();
    var date3 = (nowDate - global.confInfo.expire) * 1000; //时间差的毫秒数
    var leave1 = date3 % (24 * 3600 * 1000); //计算天数后剩余的毫秒数
    //计算相差分钟数
    var leave2 = leave1 % (3600 * 1000); //计算小时数后剩余的毫秒数
    var minutes = Math.floor(leave2 / (60 * 1000));

    // 1.校验验证码的时效
    if (global.confInfo.expire && minutes > 540) {
        ctx.status = 400;
        ctx.body = { code: 400, message: "验证码已失效，请重新获取" };
        return;
    }

    // 2.校验验证码(字母转小写，然后对比校验)
    if (code.toLowerCase() !== global.imgText) {
        ctx.status = 400;
        ctx.body = { code: 400, message: "验证码不正确!" };
        return;
    }

    // 3.校验邮箱验证码(字母转小写，然后对比校验)
    if (emailCode.toLowerCase() != global.confInfo.emailCode.toLowerCase()) {
        ctx.status = 400;
        ctx.body = { code: 400, message: "邮箱验证码不正确!" };
        return;
    }

    // 4.校验通过，重置密码
    await User.updateOne({ email }, {
        $set: {
            password: tools.enbcrypt(jsencrypt.decrypt(password)),
        },
    });
    ctx.body = { code: 200, message: "重置密码成功" };
    global.log.desc = `重置密码`;
});

module.exports = router.routes();